/*
 * test_db5.cpp
 *
 *  Created on: Apr 8, 2014
 *      Author: killerg
 */





#include "share/json/json.h"
//#include "json/json.h"

#include <time.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <libcouchbase/couchbase.h>
#include <event2/event.h>
#include <sys/signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <openssl/md5.h>

static const char* fmt_cstr(const char* fmt, ...);
static const char* to_cstr(const Json::Value &json);
static Json::Value to_json(const char* str, bool* ok = NULL);
static void on_signal(int32_t sig, void (*handler)(int));
static const char* Md5(const char* str);
static const char* hex_to_cstr(uint8_t* buf, int len);

static const int DOCNUM = 1;
static const int DOCSIZE = 1024 * 128;

static int s_pending_get = 0;
static int s_pending_set = 0;
static int s_pending_conf = 0;

static bool s_ok = false;
static bool s_run = true;
static Json::Value s_json;

static lcb_io_opt_t create_libevent_io_ops(struct event_base *evbase);
static lcb_t create_instance(lcb_io_opt_t ioops);
static void destroy_instance(lcb_t inst);
static void emit_sets(lcb_t inst);
static void emit_check(lcb_t inst);
static void gen_json();

static void handle_ctrl_c(int signal) {
    s_run = false;
}

bool test_db5() {
    on_signal(SIGINT, handle_ctrl_c);

    gen_json();
    struct event_base *evbase = event_base_new();
    lcb_io_opt_t ioops = create_libevent_io_ops(evbase);
    lcb_t inst = create_instance(ioops);

    lcb_wait(inst);
//    time_t last = time(NULL);
//    while (s_run) {
//        event_base_loop(evbase, EVLOOP_ONCE);
//
//        if (last != time(NULL)) {
//            printf("pending\n\tconf:%d\n\tset:%d\n\tget:%d\n", s_pending_conf,
//                    s_pending_set, s_pending_get);
//            printf("____________________\n");
//        }
//        last = time(NULL);
//    }

    destroy_instance(inst);
    lcb_destroy_io_ops(ioops);
    event_base_free(evbase);

    printf("pace finish\n");

    return true;
}

static void error_callback(lcb_t instance, lcb_error_t error,
        const char *errinfo) {
    fprintf(stderr, "ERROR: %s %s\n", lcb_strerror(instance, error), errinfo);
    exit(0);
    s_run = false;
}

static void configuration_callback(lcb_t inst, lcb_configuration_t config) {
    s_pending_conf = 0;
    s_ok = true;
    emit_check(inst);
}

static void store_callback(lcb_t inst, const void *cookie,
        lcb_storage_t operation, lcb_error_t error,
        const lcb_store_resp_t *resp) {
    --s_pending_set;

    if (error != LCB_SUCCESS) {
        fprintf(stderr, "Failed to store key: %s\n", lcb_strerror(inst, error));
    }
    if (s_pending_set == 0) {
        emit_check(inst);
    }
}

static void get_callback(lcb_t inst, const void *cookie, lcb_error_t error,
        const lcb_get_resp_t *resp) {
    --s_pending_get;

    if (error != LCB_SUCCESS) {
        fprintf(stderr, "Failed to get key: %s\n", lcb_strerror(inst, error));
    } else {
        std::string str((char*) resp->v.v0.bytes, resp->v.v0.nbytes);
        bool ok = false;
        Json::Value data = to_json(str.c_str(), &ok);
        if (!ok) {
            int fd = open("invalid_json", O_RDWR|O_APPEND|O_CREAT);
            if (fd >= 0) {
                assert(resp->v.v0.nbytes == write(fd, resp->v.v0.bytes, resp->v.v0.nbytes));
                close(fd);
            }
        }
        assert(ok);

        assert(data["checksum"].asString() == s_json["checksum"].asString());

    }
    if (s_pending_get == 0) {
        emit_sets(inst);
    }
}

static lcb_io_opt_t create_libevent_io_ops(struct event_base *evbase) {
    struct lcb_create_io_ops_st ciops;
    lcb_io_opt_t ioops;
    lcb_error_t error;

    memset(&ciops, 0, sizeof(ciops));
    ciops.v.v0.type = LCB_IO_OPS_LIBEVENT;
    ciops.v.v0.cookie = evbase;

    error = lcb_create_io_ops(&ioops, &ciops);
    if (error != LCB_SUCCESS) {
        fprintf(stderr,
                "Failed to create an IOOPS structure for libevent: %s\n",
                lcb_strerror(NULL, error));
        exit(EXIT_FAILURE);
    }

    return ioops;
}

static void destroy_instance(lcb_t inst) {
    if (inst != NULL) {
        lcb_destroy(inst);
    }
}

static lcb_t create_instance(lcb_io_opt_t ioops) {
    lcb_t inst;
    lcb_create_st copts("192.168.1.3", "", "", "", ioops);
    lcb_error_t err = lcb_create(&inst, &copts);
    if (err != LCB_SUCCESS) {
        fprintf(stderr, "Failed to create a libcouchbase instance: %s\n",
                lcb_strerror(NULL, err));
        return NULL;
    }

    /* Set up the callbacks */
    lcb_set_error_callback(inst, error_callback);
    lcb_set_configuration_callback(inst, configuration_callback);
    lcb_set_store_callback(inst, store_callback);
    lcb_set_get_callback(inst, get_callback);
    lcb_set_timeout(inst, 1000 * 100);

    err = lcb_connect(inst);
    if (err != LCB_SUCCESS) {
        fprintf(stderr, "Failed to connect libcouchbase instance: %s\n",
                lcb_strerror(NULL, err));
        lcb_destroy(inst);
        return NULL;
    }

    ++s_pending_conf;

    return inst;
}

static void emit_sets(lcb_t inst) {
	printf("start set\n");
    for (int i = 0; i < DOCNUM; ++i) {
        std::string value = to_cstr(s_json);
        std::string key = fmt_cstr("key:%d", i);

        lcb_store_cmd_t cmd(LCB_SET, key.c_str(), key.length(), value.c_str(),
                value.length());
        lcb_store_cmd_t *cmds[] = { &cmd };

        lcb_error_t err = lcb_store(inst, NULL, 1, cmds);
        if (err == LCB_SUCCESS) {
            ++s_pending_set;
        } else {
            fprintf(stderr, "Failed to set up store request: %s\n",
                    lcb_strerror(inst, err));
            s_run = false;
            break;
        }
    }
}

static void emit_check(lcb_t inst) {
	printf("start check\n");
    for (int i = 0; i < DOCNUM; ++i) {
        std::string key = fmt_cstr("key:%d", i);

        lcb_get_cmd_t cmd(key.c_str(), key.length());
        lcb_get_cmd_t *cmds[] = { &cmd };

        lcb_error_t err = lcb_get(inst, NULL, 1, cmds);
        if (err == LCB_SUCCESS) {
            ++s_pending_get;
        } else {
            fprintf(stderr, "Failed to setup get request: %s\n",
                    lcb_strerror(inst, err));
            s_run = false;
            break;
        }
    }
}

static void gen_json() {
    static char buffer[DOCSIZE];
    static bool inited = false;
    if (inited)
        return;

    inited = true;
    for (int i = 0; i < DOCSIZE - 1; ++i)
        buffer[i] = '0' + i % 10;
    buffer[DOCSIZE - 1] = 0;

    s_json["data"] = buffer;
    s_json["checksum"] = Md5(buffer);
}

const int BUFFSIZE = 1024 * 8;
// print to static buffer
static const char* fmt_cstr(const char* fmt, ...) {
    va_list p;
    va_start(p, fmt);

    static char buf[BUFFSIZE] = { 0 };
    vsprintf(buf, fmt, p);

    va_end(p);
    return buf;
}

static const char* to_cstr(const Json::Value &json) {
    static Json::FastWriter writer;
    static std::string tmp = writer.write(json);
    return tmp.c_str();
}

static Json::Value to_json(const char* str, bool* ok) {
    if (str == NULL || 0 == strcmp(str, "")) {
        return Json::nullValue;
        if (ok)
            *ok = false;
    }

    Json::Value json;
    Json::Reader reader;
    bool result = reader.parse(str, json);
    if (ok)
        *ok = result;

    return std::move(json);
}

static void on_signal(int32_t sig, void (*handler)(int)) {
    struct sigaction act;
    act.sa_handler = handler;
    act.sa_flags = SA_RESTART;
    sigaction(sig, &act, NULL);
}
static const char* Md5(const char* str) {
    uint8_t* buf = MD5((uint8_t*) str, strlen(str), NULL);
    return hex_to_cstr(buf, 16);
}

static const char* hex_to_cstr(uint8_t* buf, int len) {
    static char ret_buf[2048];
    for (int i = 0; i < len; ++i)
        sprintf(ret_buf + i * 2, "%02x", buf[i]);

    return ret_buf;
}

